/*
 * Copyright (c) 2011-2013, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.org).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package boofcv.alg.geo.pose;

import boofcv.alg.geo.GeoTestingOps;
import boofcv.struct.calib.IntrinsicParameters;
import boofcv.struct.calib.StereoParameters;
import boofcv.struct.sfm.Stereo2D3D;
import georegression.geometry.RotationMatrixGenerator;
import georegression.struct.point.Point2D_F64;
import georegression.struct.point.Point3D_F64;
import georegression.struct.se.Se3_F64;
import georegression.transform.se.SePointOps_F64;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * @author Peter Abeles
 */
public class CommonStereoMotionNPoint {
	protected Random rand = new Random(234);

	// the true motion
	protected Se3_F64 worldToLeft;
	protected Se3_F64 leftToRight;
	protected Se3_F64 worldToRight;


	// list of points in world reference frame
	protected List<Point3D_F64> worldPts;
	// list of points is camera reference frame
	protected List<Point3D_F64> cameraLeftPts;
	protected List<Point3D_F64> cameraRightPts;
	// list of point pairs
	protected List<Stereo2D3D> pointPose;

	protected StereoParameters param;

	public CommonStereoMotionNPoint() {
		leftToRight = new Se3_F64();
		RotationMatrixGenerator.eulerXYZ(0.01,-0.001,0.005,leftToRight.getR());
		leftToRight.getT().set(-0.1,0.02,-0.03);

		param = new StereoParameters();
		param.rightToLeft = leftToRight.invert(null);

		param.left = new IntrinsicParameters(400,500,0.1,160,120,320,240,false,new double[]{0,0});
		param.right = new IntrinsicParameters(380,505,0.05,165,115,320,240,false,new double[]{0,0});

		worldToLeft = new Se3_F64();
		RotationMatrixGenerator.eulerXYZ(0.01, 0.04, -0.05, worldToLeft.getR());
		worldToLeft.getT().set(0.1,-0.1,0.2);

		worldToRight = worldToLeft.concat(leftToRight,null);
	}

	protected void generateScene(int N, Se3_F64 worldToLeft, boolean planar) {
		if( worldToLeft == null ) {
			this.worldToLeft = worldToLeft = new Se3_F64();
			RotationMatrixGenerator.eulerXYZ(0.1, 1, -0.2, worldToLeft.getR());
			worldToLeft.getT().set(-0.3,0.4,1);
		} else {
			this.worldToLeft = worldToLeft;
		}

		// randomly generate points in space
		if( planar ) {
			worldPts = createRandomPlane(rand, 3, N);
		} else {
			worldPts = GeoTestingOps.randomPoints_F64(-1, 1, -1, 1, 2, 3, N, rand);
		}

		cameraLeftPts = new ArrayList<Point3D_F64>();
		cameraRightPts = new ArrayList<Point3D_F64>();

		// transform points into second camera's reference frame
		pointPose = new ArrayList<Stereo2D3D>();
		for(Point3D_F64 p1 : worldPts ) {
			Point3D_F64 leftPt = SePointOps_F64.transform(worldToLeft, p1, null);
			Point3D_F64 rightPt = SePointOps_F64.transform(leftToRight, leftPt, null);

			Point2D_F64 leftObs = new Point2D_F64(leftPt.x/leftPt.z,leftPt.y/leftPt.z);
			Point2D_F64 rightObs = new Point2D_F64(rightPt.x/rightPt.z,rightPt.y/rightPt.z);

			pointPose.add( new Stereo2D3D(leftObs,rightObs,p1));

			cameraLeftPts.add(leftPt);
			cameraRightPts.add(rightPt);
		}
	}

	public void addNoise( double sigma ) {
		for( Stereo2D3D o : pointPose ) {
			o.leftObs.x += rand.nextGaussian()*sigma;
			o.leftObs.y += rand.nextGaussian()*sigma;
			o.rightObs.x += rand.nextGaussian()*sigma;
			o.rightObs.y += rand.nextGaussian()*sigma;
		}
	}

	/**
	 * Creates a set of random points along the (X,Y) plane
	 */
	public static List<Point3D_F64> createRandomPlane( Random rand , double d , int N )
	{
		List<Point3D_F64> ret = new ArrayList<Point3D_F64>();

		for( int i = 0; i < N; i++ ) {
			double x = (rand.nextDouble()-0.5)*2;
			double y = (rand.nextDouble()-0.5)*2;

			ret.add( new Point3D_F64(x,y,d));
		}

		return ret;
	}
}
